# 引入各种功能组件
import contextlib
import logging # 日志
from collections.abc import AsyncIterator

import anyio
import click # 命令行参数 --api-key=xxx
import httpx # 提供让大模型调用工具的能力
# mcp提供让大模型调用工具的能力
import mcp.types as types
from mcp.server.lowlevel import Server
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
# 搭建http网络服务
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.types import Receive, Scope, Send

# ---------------------------------------------------------------------------
# 查询天气的函数
# ---------------------------------------------------------------------------
OPENWEATHER_URL = "https://api.openweathermap.org/data/2.5/weather" # openweather接口地址
DEFAULT_UNITS = "metric"  # 默认使用摄氏温度
DEFAULT_LANG = "zh_cn"  # 中文


async def fetch_weather(city: str, api_key: str) -> dict[str, str]:
    """
    调用OpenWeather API并返回一个简化的天气字典。

    Raises:
        httpx.HTTPStatusError: if the response has a non-2xx status.
    """
    # 设置请求参数
    params = {
        "q": city,
        "appid": api_key,
        "units": DEFAULT_UNITS,
        "lang": DEFAULT_LANG,
    }
    # 异步网络请求
    async with httpx.AsyncClient(timeout=10) as client:
        r = await client.get(OPENWEATHER_URL, params=params)
        r.raise_for_status()
        data = r.json()
    # 获取返回结果中的参数
    weather_main = data["weather"][0]["main"]
    description = data["weather"][0]["description"]
    temp = data["main"]["temp"]
    feels_like = data["main"]["feels_like"]
    humidity = data["main"]["humidity"]
    # 函数返回结果 字典类型
    return {
        "city": city,
        "weather": weather_main,
        "description": description,
        "temp": f"{temp}°C",
        "feels_like": f"{feels_like}°C",
        "humidity": f"{humidity}%",
    }

# 命令函参数

@click.command()
@click.option(
    "--port", 
    default=3000, 
    help="HTTP服务监听的端口号",
    type=int,  # 参数类型
    show_default=True  # 显示默认值
)
@click.option(
    "--api-key",
    envvar="OPENWEATHER_API_KEY",  # 支持从环境变量读取
    required=True,  # 不强制要求必须提供
    help="OpenWeather API密钥",
    metavar="KEY"  # 帮助信息中的参数占位符
)
@click.option(
    "--log-level",
    default="INFO",
    help="日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
    type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], case_sensitive=False)  # 限定可选值
)
@click.option(
    "--json-response",
    is_flag=True,  # 标记为布尔标志
    default=False,
    help="启用JSON响应格式（默认使用SSE流式传输）",
    show_default=True
)


def main(port: int, api_key: str, log_level: str, json_response: bool) -> int:
    """使用流式传输协议运行一个天气查询MCP服务"""

    # ----------------------日志配置 ----------------------
    logging.basicConfig(
        level=getattr(logging, log_level.upper()),
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    )
    logger = logging.getLogger("weather-server")

    # ---------------------- 创建mcp服务 ----------------------
    app = Server("mcp-streamable-http-weather")

    # ---------------------- 工具实现 -------------------
    # MCP工具注册：让大模型能调用 get-weather
    @app.call_tool() # 装饰器声明这是一个工具调用端点
    
    # 异步设计：async def 支持非阻塞IO（如网络请求）
    # 输入参数
    #    name：工具名称（如"get-weather"）
    #    arguments：大模型传入的参数字典（如{"location": "北京"}）
    # 返回值：标准化返回TextContent类型列表（兼容多模态扩展）
    
    async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
        """Handle the 'get-weather' tool call."""
        # 获取当前请求的上下文对象（request context）
        ctx = app.request_context
        city = arguments.get("location")
        if not city:
            raise ValueError("'location' is required in arguments")

        # 发送初始日志消息，以便客户端尽早看到流。
        await ctx.session.send_log_message(
            level="info",
            data=f"Fetching weather for {city}…",
            logger="weather", #  # 日志分类
            related_request_id=ctx.request_id, # 关联请求ID
        )

        try:
            weather = await fetch_weather(city, api_key)
        except Exception as err:
            # 将错误信息流式传输至客户端并重新抛出异常，以便MCP协议能够返回错误状态。
            '''
            raise 重新抛出当前捕获的异常
            双重处理：实现「日志记录 + 协议层错误处理」的分离：
                本地处理：先通过send_log_message将错误详情流式传输给客户端
                全局处理：再通过raise让MCP协议层捕获并返回标准化错误响应
            '''
            await ctx.session.send_log_message(
                level="error",
                data=str(err),
                logger="weather",
                related_request_id=ctx.request_id,
            )
            raise

        # 流式传输成功状态通知（可选）
        await ctx.session.send_log_message(
            level="info",
            data="Weather data fetched successfully!",
            logger="weather",
            related_request_id=ctx.request_id,
        )

        # 为最终返回值编写可读的总结
        summary = (
            f"{weather['city']}：{weather['description']}，温度 {weather['temp']}，"
            f"体感 {weather['feels_like']}，湿度 {weather['humidity']}。"
        )

        return [
            types.TextContent(type="text", text=summary),
        ]

    # ---------------------- 工具注册 -------------------------
    @app.list_tools()
    async def list_tools() -> list[types.Tool]:
        """保留可用的工具给大模型"""
        return [
            types.Tool(
                name="get-weather",
                description="查询指定城市的实时天气（OpenWeather 数据）",
                inputSchema={
                    "type": "object",
                    "required": ["location"],
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "城市的英文名称，如 'Beijing'",
                        }
                    },
                },
            )
        ]

    # ---------------------- 会话管理 -----------------------
    # 创建MCP的“HTTP 会话处理中心”，负责处理所有/mcp 路由的请求

    # json_response=False 表示用流式 SSE（也可以改成一次性 JSON 响应）。
    session_manager = StreamableHTTPSessionManager(
        app=app,
        event_store=None,  # 无状态；不保存历史事件
        json_response=json_response,
        stateless=True,
    )
    # 实现ASGI（Asynchronous Server Gateway Interface）服务的核心生命周期管理
    async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> None: 
        await session_manager.handle_request(scope, receive, send)

    # ---------------------- Lifespan Management 生命周期管理 --------------------
    @contextlib.asynccontextmanager
    async def lifespan(app: Starlette) -> AsyncIterator[None]:
        async with session_manager.run():
            logger.info("Weather MCP server started! 🚀")
            try:
                yield
            finally:
                logger.info("Weather MCP server shutting down…")

    # ---------------------- ASGI app + Uvicorn ---------------------
    #  ASGI 应用的最终组装和服务器启动
    starlette_app = Starlette(
        debug=False, # 禁用调试模式（生产环境）
        routes=[Mount("/mcp", app=handle_streamable_http)], # 路由挂载
        lifespan=lifespan, # 注入生命周期管理器
    )

    import uvicorn

    uvicorn.run( 
        starlette_app,  # ASGI应用实例
        host="0.0.0.0", # 监听所有网络接口
        port=port # 使用参数传入的端口号
    ) 

    return 0


if __name__ == "__main__":
    main()